/*
 ------------------------------------------------------------------------------
 Copyright (C) 1996-2001 Id Software, Inc.

 This file is part of the Quake source code.

 The Quake source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quake source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quake source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// sphere.h - sphere math library
//


#ifndef __MATH_SPHERE_H__
#define __MATH_SPHERE_H__


extern glqSphere		sphere_zero;

class glqSphere {
private:
	glqVec3			center;
	float			radius;

public:
					glqSphere (void);
	explicit		glqSphere (const glqVec3 &center, const float radius);
	explicit		glqSphere (const float x, const float y, const float z, const float radius);

	float			operator [] (const int index) const;
	float &			operator [] (const int index);

	glqSphere &		operator = (const glqSphere &sphere);
	bool			operator == (const glqSphere &sphere) const;
	bool			operator != (const glqSphere &sphere) const;

	glqSphere		operator + (const glqSphere &sphere) const;
	glqSphere		operator + (const glqVec3 &vec) const;
	glqSphere		operator - (const glqVec3 &vec) const;

	glqSphere &		operator += (const glqSphere &sphere);
	glqSphere &		operator += (const glqVec3 &vec);
	glqSphere &		operator -= (const glqVec3 &vec);

	void			Set (const glqVec3 &center, const float radius);
	void			Set (const float x, const float y, const float z, const float radius);
	void			Clear (void);
	bool			IsCleared (void) const;
	void			Zero (void);
	bool			IsZeroed (void) const;
	void			AddSphere (const glqSphere &sphere);
	void			AddPoint (const glqVec3 &point);
	glqSphere		Expand (const float size) const;
	void			ExpandSelf (const float size);
	glqSphere		Translate (const glqVec3 &origin) const;
	void			TranslateSelf (const glqVec3 &origin);
	bool			IntersectsSphere (const glqSphere &sphere) const;
	bool			IntersectsBounds (const glqVec3 &mins, const glqVec3 &maxs) const;
	bool			IntersectsLine (const glqVec3 &start, const glqVec3 &end) const;
	bool			ContainsPoint (const glqVec3 &point) const;

	const glqVec3 &	Center (void) const;
	float			Radius (void) const;

	glqVec4			ToVec4 (void) const;
	void			FromVec4 (const glqVec4 &vec);

	const float *	ToFloatPtr (void) const;
	float *			ToFloatPtr (void);

	glqBounds		ToBounds (void) const;
	const char *	ToString (int precision = 2) const;
};

INLINE glqSphere::glqSphere (void){

}

INLINE glqSphere::glqSphere (const glqVec3 &center, const float radius){

	this->center = center;
	this->radius = radius;
}

INLINE glqSphere::glqSphere (const float x, const float y, const float z, const float radius){

	this->center.x = x;
	this->center.y = y;
	this->center.z = z;
	this->radius = radius;
}

INLINE float glqSphere::operator [] (const int index) const {

	return ((float *)&center)[index];
}

INLINE float &glqSphere::operator [] (const int index){

	return ((float *)&center)[index];
}

INLINE glqSphere &glqSphere::operator = (const glqSphere &sphere){

	center = sphere.center;
	radius = sphere.radius;

	return *this;
}

INLINE bool glqSphere::operator == (const glqSphere &sphere) const {

	if (center != sphere.center || radius != sphere.radius)
		return false;

	return true;
}

INLINE bool glqSphere::operator != (const glqSphere &sphere) const {

	if (center == sphere.center && radius == sphere.radius)
		return false;

	return true;
}

INLINE glqSphere glqSphere::operator + (const glqSphere &sphere) const {

	glqSphere	result;
	glqVec3		dir;
	float		len;

	if (radius < 0.0f){
		result.center = sphere.center;
		result.radius = sphere.radius;

		return result;
	}

	dir = sphere.center - center;
	len = dir.LengthSqr();

	if (len > Square(sphere.radius + radius)){
		len = glqMath::Sqrt(len);

		result.center = center + dir * 0.5f * (1.0f - radius / (sphere.radius + len));
		result.radius = radius + 0.5f * ((sphere.radius + len) - radius);
	}
	else {
		result.center = center;
		result.radius = radius;
	}

	return result;
}

INLINE glqSphere glqSphere::operator + (const glqVec3 &vec) const {

	return glqSphere(center + vec, radius);
}

INLINE glqSphere glqSphere::operator - (const glqVec3 &vec) const {

	return glqSphere(center - vec, radius);
}

INLINE glqSphere &glqSphere::operator += (const glqSphere &sphere){

	glqVec3	dir;
	float	len;

	if (radius < 0.0f){
		center = sphere.center;
		radius = sphere.radius;

		return *this;
	}

	dir = sphere.center - center;
	len = dir.LengthSqr();

	if (len > Square(sphere.radius + radius)){
		len = glqMath::Sqrt(len);

		center += dir * 0.5f * (1.0f - radius / (sphere.radius + len));
		radius += 0.5f * ((sphere.radius + len) - radius);
	}

	return *this;
}

INLINE glqSphere &glqSphere::operator += (const glqVec3 &vec){

	center += vec;

	return *this;
}

INLINE glqSphere &glqSphere::operator -= (const glqVec3 &vec){

	center -= vec;

	return *this;
}

INLINE void glqSphere::Set (const glqVec3 &center, const float radius){

	this->center = center;
	this->radius = radius;
}

INLINE void glqSphere::Set (const float x, const float y, const float z, const float radius){

	this->center.x = x;
	this->center.y = y;
	this->center.z = z;
	this->radius = radius;
}

INLINE void glqSphere::Clear (void){

	center.Clear();
	radius = -M_INFINITY;
}

INLINE bool glqSphere::IsCleared (void) const {

	if (radius >= 0.0f)
		return false;

	return true;
}

INLINE void glqSphere::Zero (void){

	center.Clear();
	radius = 0.0f;
}

INLINE bool glqSphere::IsZeroed (void) const {

	if (center[0] != 0.0f || center[1] != 0.0f || center[2] != 0.0f)
		return false;
	if (radius != 0.0f)
		return false;

	return true;
}

INLINE void glqSphere::AddSphere (const glqSphere &sphere){

	glqVec3	dir;
	float	len;

	if (radius < 0.0f){
		center = sphere.center;
		radius = sphere.radius;

		return;
	}

	dir = sphere.center - center;
	len = dir.LengthSqr();

	if (len > Square(sphere.radius + radius)){
		len = glqMath::Sqrt(len);

		center += dir * 0.5f * (1.0f - radius / (sphere.radius + len));
		radius += 0.5f * ((sphere.radius + len) - radius);
	}
}

INLINE void glqSphere::AddPoint (const glqVec3 &point){

	glqVec3	dir;
	float	len;

	if (radius < 0.0f){
		center = point;
		radius = 0.0f;

		return;
	}

	dir = point - center;
	len = dir.LengthSqr();

	if (len > Square(radius)){
		len = glqMath::Sqrt(len);

		center += dir * 0.5f * (1.0f - radius / len);
		radius += 0.5f * (len - radius);
	}
}

INLINE glqSphere glqSphere::Expand (const float size) const {

	return glqSphere(center, radius + size);
}

INLINE void glqSphere::ExpandSelf (const float size){

	radius += size;
}

INLINE glqSphere glqSphere::Translate (const glqVec3 &origin) const {

	return glqSphere(center + origin, radius);
}

INLINE void glqSphere::TranslateSelf (const glqVec3 &origin){

	center += origin;
}

INLINE bool glqSphere::IntersectsSphere (const glqSphere &sphere) const {

	if ((sphere.center - center).LengthSqr() > Square(sphere.radius + radius))
		return false;

	return true;
}

INLINE bool glqSphere::IntersectsBounds (const glqVec3 &mins, const glqVec3 &maxs) const {

	float	d = 0.0f;

	if (center[0] < mins[0])
		d += Square(center[0] - mins[0]);
	else if (center[0] > maxs[0])
		d += Square(maxs[0] - center[0]);

	if (center[1] < mins[1])
		d += Square(center[1] - mins[1]);
	else if (center[1] > maxs[1])
		d += Square(maxs[1] - center[1]);

	if (center[2] < mins[2])
		d += Square(center[2] - mins[2]);
	else if (center[2] > maxs[2])
		d += Square(maxs[2] - center[2]);

	if (d > Square(radius))
		return false;

	return true;
}

INLINE bool glqSphere::IntersectsLine (const glqVec3 &start, const glqVec3 &end) const {

	glqVec3	s, e;
	glqVec3	dir;
	float	d;

	s = start - center;
	e = end - center;

	dir = end - start;

	d = -dir.Dot(s);

	if (d <= 0.0f){
		if (s.LengthSqr() > Square(radius))
			return false;

		return true;
	}

	if (d >= dir.LengthSqr()){
		if (e.LengthSqr() > Square(radius))
			return false;

		return true;
	}

	dir = s + dir * (d / dir.LengthSqr());

	if (dir.LengthSqr() > Square(radius))
		return false;

	return true;
}

INLINE bool glqSphere::ContainsPoint (const glqVec3 &point) const {

	if ((point - center).LengthSqr() > Square(radius))
		return false;

	return true;
}

INLINE const glqVec3 &glqSphere::Center (void) const {

	return center;
}

INLINE float glqSphere::Radius (void) const {

	return radius;
}

INLINE const float *glqSphere::ToFloatPtr (void) const {

	return center.ToFloatPtr();
}

INLINE float *glqSphere::ToFloatPtr (void){

	return center.ToFloatPtr();
}


#endif	// __MATH_SPHERE_H__